#include <QTcpSocket>
#include <QXmlStreamReader>
#include <QDebug>
#include <QThread>
#include <QByteArray>
#include <QSqlQuery>
#include <QSqlRecord>
#include <unordered_map>
#include <QDate>
#include <limits>

#include "DataBaseAccessor.hpp"

#include "Client.hpp"

Client::Client(int socketDescriptor,
               QObject *parent) :
    m_socketDescriptor(socketDescriptor),
    m_userRight(NOT_AUTHORIZED),
    QObject(parent)
{
    if(socketDescriptor) {
        m_tcpSocket = new QTcpSocket(this);
        m_tcpSocket->setSocketDescriptor(socketDescriptor);
        connect(m_tcpSocket, SIGNAL(readyRead()),
                this, SLOT(onReadyRead()));
        connect(m_tcpSocket, SIGNAL(disconnected()),
                this, SLOT(onDisconnected()));
    }
}

Client::~Client()
{
    if(m_tcpSocket)
        m_tcpSocket->deleteLater();
}

QTcpSocket *Client::socket() const
{
    return m_tcpSocket;
}

const int Client::socketDescriptor() const
{
    return m_socketDescriptor;
}

void Client::writeXmlMessageSize(QByteArray &data)
{
    QString dataLength = QString::number(data.size());
    while(dataLength.length() < std::numeric_limits<quint32>::digits10) {
        dataLength.prepend(QString("0"));
    }
    data.prepend(dataLength.toStdString().c_str(), dataLength.toStdString().size());
}

void Client::parseXml(QByteArray &data)
{
    QXmlStreamReader xml(data);

    while (!xml.atEnd()) {
        QXmlStreamReader::TokenType tokenType = xml.readNext();
        if (QXmlStreamReader::StartDocument == tokenType) {
            continue;
        }

        if (QXmlStreamReader::StartElement == tokenType) {
            if (trUtf8("command") == xml.name()) {
                this->parseCommand(xml);
            }
        }
    }
}

void Client::parseCommand(QXmlStreamReader &xml)
{
    /*if (COMMAND_AUTHORISATION ==
            xml.attributes().value(trUtf8("type")).toString().toShort()) {
        qDebug() << "Parsing authorisation";
        this->parseAuthorisation(xml);

    }*/
    switch (xml.attributes().value(trUtf8("type")).toString().toShort()) {
    case COMMAND_AUTHORISATION:
        qDebug() << "Parsing authorisation";
        this->parseAuthorisation(xml);
        break;

    case COMMAND_GET_ALL:
        qDebug() << "Parsing get all";
        this->parseAllTransport(xml);
        break;

    case COMMAND_SEARCH_ON_NEXT_MONTH:
        qDebug() << "Parsing search on next month";
        this->parseSearchOnNextMonth(xml);
        break;
    default:
        xml.clear();
    }

    //else if

    /*if (xml.attributes().value("type") == "authorisation") {
        QString name;
        QString password;
        while(!xml.atEnd()) {
            xml.readNext();
            if("name" == xml.name())
                name = xml.text().toString();
            if("password" == xml.name())
                password = xml.text().toString();
        }
        this->authorisationQuery(name, password);
    }*/
}

void Client::parseAuthorisation(QXmlStreamReader &xml)
{
    QString name;
    QString password;
    while (!xml.atEnd()) {
        xml.readNext();
        if (trUtf8("name") == xml.name() &&
                QXmlStreamReader::StartElement == xml.tokenType()) {
            xml.readNext();
            if (QXmlStreamReader::Characters == xml.tokenType()) {
                name = xml.text().toString();
            }
        }
        if (trUtf8("password") == xml.name() &&
                QXmlStreamReader::StartElement == xml.tokenType()) {
            xml.readNext();
            if (QXmlStreamReader::Characters == xml.tokenType()) {
                password = xml.text().toString();
            }
        }
    }
    qDebug() << "Name:" << name << "Password" << password;
    queryAuthorisation(name, password);
}

void Client::parseAllTransport(QXmlStreamReader &xml)
{
    queryAllTransport();
}

void Client::parseSearchOnNextMonth(QXmlStreamReader &xml)
{
    QString date;
    while (!xml.atEnd()) {
        if (QXmlStreamReader::StartElement == xml.tokenType() &&
                trUtf8("date") == xml.name()) {
            xml.readNext();
            if (QXmlStreamReader::Characters == xml.tokenType()) {
                date = xml.text().toString();
            }
        }
    }
    qDebug() << "Date:" << date;
    querySearchOnNextMonth(date);
}

void Client::queryAuthorisation(const QString &name, const QString &password)
{
    QString queryString = "SELECT `user_right` FROM `users` WHERE `name` = '" + name
            + "' AND `password` = '" + password + "'";
    /*QString queryString = "SELECT * FROM users WHERE name = '" + name
            + "' AND password = MD5('" + password + "')";*/
    qDebug() << "Authorisation query:" << queryString;
    emit query(queryString, m_socketDescriptor, COMMAND_AUTHORISATION);
}

void Client::querySearchOnNextMonth(const QString &date)
{
    //QString queryString = ;
}

void Client::queryAllTransport()
{
    QString queryString =
            "SELECT transport_card.id, transport_card.id_category, "
            "category.category, transport_card.id_construction, "
            "construction.construction, transport_card.id_transport, "
            "transport.id_mark, mark.mark, transport.id_model, "
            "model.model, transport_card.id_country, country.name, "
            "transport_card.id_color, color.color, "
            "transport_card.serial_num, transport_card.board_num, "
            "transport_card.release_date, "
            "transport_card.last_inspection_date, "
            "transport_card.owner_name, transport_card.driver_lic_num, "
            "transport_card.passport_num \n"
            "FROM color "
            "RIGHT JOIN ( "
                "( "
                    "model "
                    "RIGHT JOIN ( "
                        "mark "
                        "RIGHT JOIN transport ON mark.id = transport.id_mark "
                    ") ON model.id = transport.id_model "
                ") "
                "RIGHT JOIN ( "
                    "country "
                    "RIGHT JOIN ( "
                        "construction "
                        "RIGHT JOIN ( "
                            "category "
                            "RIGHT JOIN transport_card ON category.id = transport_card.id_category "
                        ") ON construction.id = transport_card.id_construction "
                    ") ON country.id_country = transport_card.id_country "
                ") ON transport.id = transport_card.id_transport "
            ") ON color.id = transport_card.id_color ";
    qDebug() << "Query:" << queryString;
    emit query(queryString, m_socketDescriptor, COMMAND_GET_ALL);
}

void Client::answerAuthorisation(QSqlQuery &query)
{
    if (query.next()) {
        qDebug() << "User in database";
        auto userRight = query.value(0).toString();
        qDebug() << userRight;
        if (trUtf8("ADMIN") == userRight) {
            m_userRight = ADMIN;
        }
        else if (trUtf8("OPERATOR") == userRight) {
            m_userRight = OPERATOR;
        }
        else if (trUtf8("REGISTRATOR") == userRight) {
            m_userRight = REGISTRATOR;
        }
        else {
            m_userRight = NOT_AUTHORIZED;
        }
    }
    else {
        qDebug() << "User not in database";
        m_userRight = NOT_AUTHORIZED;
    }

    QByteArray data;
    QXmlStreamWriter stream(&data);

    stream.setAutoFormatting(true);

    stream.writeStartDocument();

    stream.writeStartElement(trUtf8("command"));
    stream.writeAttribute(trUtf8("type"),
                          QString::number(static_cast<quint16>(COMMAND_AUTHORISATION)));

    stream.writeStartElement(trUtf8("parametrs"));
    stream.writeTextElement(trUtf8("userRight"),
                            QString::number(static_cast<quint16>(m_userRight)));

    stream.writeEndElement(); //parametrs

    stream.writeEndElement(); //command

    stream.writeEndDocument();

    auto dataLength = QString::number(data.size());
    while(dataLength.length() < std::numeric_limits<quint32>::digits10) {
        dataLength.prepend(QString("0"));
    }
    data.prepend(dataLength.toStdString().c_str(),
                 dataLength.toStdString().size());
    qDebug() << data;

    m_tcpSocket->write(data);
}

void Client::answerAllTransport(QSqlQuery &query)
{
    QByteArray data;
    QXmlStreamWriter xml(&data);

    xml.setAutoFormatting(true);

    xml.writeStartDocument();

    xml.writeStartElement(trUtf8("command"));
    xml.writeAttribute(trUtf8("type"),
                          QString::number(static_cast<quint16>(COMMAND_GET_ALL)));
    xml.writeStartElement(trUtf8("parametrs"));
    xml.writeStartElement(trUtf8("array"));
    quint32 i = 0;
    while (query.next()) {
        xml.writeStartElement("transportCard");
        xml.writeAttribute("index", QString::number(i));
        xml.writeTextElement("transportId", query.value(0).toString());
        xml.writeTextElement("categoryId", query.value(1).toString());
        xml.writeTextElement("categoryName", query.value(2).toString());
        xml.writeTextElement("constructionId", query.value(3).toString());
        xml.writeTextElement("constructionName", query.value(4).toString());
        xml.writeTextElement("transportId", query.value(5).toString());
        xml.writeTextElement("markId", query.value(6).toString());
        xml.writeTextElement("markName", query.value(7).toString());
        xml.writeTextElement("modelId", query.value(8).toString());
        xml.writeTextElement("modelName", query.value(9).toString());
        xml.writeTextElement("countryId", query.value(10).toString());
        xml.writeTextElement("countryName", query.value(11).toString());
        xml.writeTextElement("colorId", query.value(12).toString());
        xml.writeTextElement("colorName", query.value(13).toString());
        xml.writeTextElement("serialNum", query.value(14).toString());
        xml.writeTextElement("boardNum", query.value(15).toString());
        xml.writeTextElement("releaseDate", query.value(16).toString());
        xml.writeTextElement("lastInspectionDate", query.value(17).toString());
        xml.writeTextElement("ownerName", query.value(18).toString());
        xml.writeTextElement("driverLicNum", query.value(19).toString());
        xml.writeTextElement("passportNum", query.value(20).toString());
        xml.writeEndElement();
        ++i;
    }
    xml.writeEndElement();//array
    xml.writeEndElement();//parametrs
    xml.writeEndElement();//command
    xml.writeEndDocument();

    writeXmlMessageSize(data);

    qDebug() << data;
}


void Client::onReadyRead()
{
    QTcpSocket* socket
            = qobject_cast<QTcpSocket*>(sender());

    if (!socket)
        return;
    auto bytesAvailable = socket->bytesAvailable();
    if (bytesAvailable < sizeof(std::numeric_limits<quint32>::digits10))
        return;
    auto messageSize = socket->peek(std::numeric_limits<quint32>::digits10).toLongLong();
    if ((bytesAvailable - std::numeric_limits<quint32>::digits10) < messageSize)
        return;

    QByteArray data = m_tcpSocket->read(std::numeric_limits<quint32>::digits10);
    messageSize = data.toLongLong();
    data.clear();

    static const quint16 bufferSize = 1024;
    char buffer[bufferSize];
    auto leftBytes = m_tcpSocket->bytesAvailable();
    quint32 currentReadSize;

    (leftBytes >= bufferSize) ? currentReadSize = bufferSize
                              : currentReadSize = leftBytes;
    while (leftBytes) {
        auto readBytes = m_tcpSocket->read(buffer, currentReadSize);
        data.append(buffer, readBytes);
        leftBytes -= readBytes;
    }

    qDebug() << data;

    this->parseXml(data);
}

void Client::onDisconnected()
{
    emit disconnected();
}

/*
void Client::readData()
{
    volatile int rnd;
    static const quint16 bufferSize = 256;
    quint16 currentReadSize;
    char buffer[bufferSize];
    QByteArray data = m_tcpSocket->read(std::numeric_limits<quint32>::digits10);
    auto leftToReadBytes = data.toLongLong();
    data.clear();
    currentReadSize = bufferSize;
    while (leftToReadBytes != 0) {
        auto bytesReadSize= m_tcpSocket->read(buffer, currentReadSize);
        leftToReadBytes -= bytesReadSize;
        data.append(buffer, bytesReadSize);
        if (leftToReadBytes < currentReadSize) {
            currentReadSize = leftToReadBytes;
        }
    }
    /*qDebug() << "work done" << m_tcpSocket->socketDescriptor()
                << data;*/
/*
    qDebug() << data;
    QXmlStreamReader stream(data);
    while(!stream.atEnd()) {
        QXmlStreamReader::TokenType token = stream.readNext();
        if (token == QXmlStreamReader::StartDocument) {
            continue;
        }
        if(token == QXmlStreamReader::StartElement) {

            if(stream.name() == QString::fromUtf8(tr("command").toStdString().c_str())) {
                this->parseCommand(stream);
            }
        }
    }
    emit workDone();
}
*/


void Client::queryAnswer(QSqlQuery query, quint32 socket, COMMAND commandName)
{
    if (socket != this->m_socketDescriptor)
        return;
    switch (commandName) {
    case COMMAND_AUTHORISATION:
        qDebug() << "queryAnswer: Authorisation";
        answerAuthorisation(query);
        break;

    case COMMAND_GET_ALL:
        qDebug() << "queryAnswer: GET_ALL";
        answerAllTransport(query);
    default:
        qDebug() << "queryAnswer: INVALID COMMAND";
        break;
    }

}

